package org.example.util;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Objects;

/**
 * @author Aaron
 * @since 2019/4/1 9:44
 * <p>描述：JDK8 及以上日期工具类</p>
 * <p>
 * 时刻：所有计算机是一致的，都用从 1970.1.1 00:00:00 到当前经过的秒数表示。但因为时区不同，
 * 所以不同时区的计算机上显示的年月日时分秒是不同的。也就是说，同一时刻，所有电脑上调用
 * {@link System#currentTimeMillis()} 方法返回的整数是相等的。
 * <p>
 * 解析：
 * Instant，表示时刻，
 * LocalDateTime，表示与时区无关的日期和时间信息，不直接对应时刻，需要通过时区转换
 * LocalDate，表示与时区无关的日期，只有日期信息，没有时间信息
 * LocalTime，表示与时区无关的时间，只有时间信息，没有日期信息
 * ZonedDateTime，表示特定的时区日期和时间
 * ZoneId / ZoneOffset，表示时区
 */
@SuppressWarnings("unused")
public class DateUtil {

    public static void main(String[] args) {
        System.out.println(Plus.daysFormat(5, DateUtil.PATTERN_DATE_2));

        System.out.println(toShowString(49 * 3600 + 3661, ChronoUnit.SECONDS));

        Date date = DateUtil.parse("2020-02-20 01:01:01", "yyyy-MM-dd HH:mm:ss");
        Date date2 = DateUtil.parse("2020-03-21 02:02:02", "yyyy-MM-dd HH:mm:ss");

        // 1 加减法测试
        // 对于天，时，分，秒， JDK(Instant) 和 joda-time 均支持
        // 对周，月，年，JDK(LocalDateTime) 和 joda-time 均支持，但 JDK(Instant) 不支持
        System.out.println(date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().minus(1, ChronoUnit.WEEKS));
        System.out.println(Date.from(date.toInstant().minus(1, ChronoUnit.DAYS)));
        // System.out.println(new DateTime(date).minusWeeks(1).toDate());
        System.out.println(date);

        // 2 计算时间间隔，结果同上，在 JDK 中，Instant 支持天、时、分、秒，LocalDateTime 支持全部单位
        System.out.println();
        // System.out.println(Seconds.secondsBetween(new DateTime(date), new DateTime(date2)).getSeconds());
        System.out.println(Between.seconds(date, date2));
        // System.out.println(Weeks.weeksBetween(new DateTime(date), new DateTime(date2)).getWeeks());
        System.out.println(Between.weeks(date, date2));

        // 3 of，JDK 和 joda-time 均支持所有单位
        System.out.println();
        // System.out.println(new DateTime(date).getSecondOfDay());
        System.out.println(Of.secondOfDay(date));


    }

    /**
     * 默认模式(包含毫秒)
     */
    public static final String DEFAULT_PATTERN_MS = "yyyy-MM-dd HH:mm:ss.SSS";
    /**
     * 默认模式(无毫秒)
     */
    public static final String DEFAULT_PATTERN = "yyyy-MM-dd HH:mm:ss";
    /**
     * 默认模式(无分隔符)
     */
    public static final String DEFAULT_PATTERN_NO_SEPARATOR = "yyyyMMddHHmmss";
    /**
     * 默认模式(无分隔符，且包含毫秒)
     */
    public static final String DEFAULT_PATTERN_MS_NO_SEPARATOR = "yyyyMMddHHmmssSSS";
    /**
     * 默认日期模式
     */
    public static final String PATTERN_DATE = "yyyy-MM-dd";
    /**
     * 默认日期模式
     */
    public static final String PATTERN_DATE_2 = "yyyy年MM月dd日";
    /**
     * 默认时间模式
     */
    public static final String PATTERN_TIME = "HH:mm:ss";

    public static class Plus {
        public static Date days(int offset) {
            return Date.from(Instant.now().plus(offset, ChronoUnit.DAYS));
        }

        public static Date days(Date date, int offset) {
            return Date.from(date.toInstant().plus(offset, ChronoUnit.DAYS));
        }

        public static String daysFormat(Date date, int offset) {
            Date resultDate = Date.from(date.toInstant().plus(offset, ChronoUnit.DAYS));
            return format(resultDate, PATTERN_DATE);
        }

        public static String daysFormat(int offset, String pattern) {
            Date resultDate = Date.from(Instant.now().plus(offset, ChronoUnit.DAYS));
            return format(resultDate, pattern);
        }

        public static String daysFormat(Date date, int offset, String pattern) {
            Date resultDate = Date.from(date.toInstant().plus(offset, ChronoUnit.DAYS));
            return format(resultDate, pattern);
        }
    }

    /**
     * 时间差计算
     * <p>
     * 这里支持年、月、周、时、分、秒的计算。只提供 {@link Date} 为参数的计算，其它参数需
     * 要先使用 {@link Convert} 转换后再进行计算。
     */
    public static class Between {

        /**
         * 获取时间差 - 秒
         *
         * @param minuendDate    被减数
         * @param subtrahendDate 减数
         * @return 相差的秒数
         */
        public static Long seconds(Date minuendDate, Date subtrahendDate) {
            return ChronoUnit.SECONDS.between(minuendDate.toInstant(), subtrahendDate.toInstant());
        }

        /**
         * 获取时间差 - 分钟
         *
         * @param minuendDate    被减数
         * @param subtrahendDate 减数
         * @return 相差的分钟数
         */
        public static Long minutes(Date minuendDate, Date subtrahendDate) {
            return ChronoUnit.MINUTES.between(minuendDate.toInstant(), subtrahendDate.toInstant());
        }

        /**
         * 获取时间差 - 小时
         *
         * @param minuendDate    被减数
         * @param subtrahendDate 减数
         * @return 相差的秒数
         */
        public static Long hours(Date minuendDate, Date subtrahendDate) {
            return ChronoUnit.HOURS.between(minuendDate.toInstant(), subtrahendDate.toInstant());
        }

        /**
         * 获取时间差 - 天
         *
         * @param minuendDate    被减数
         * @param subtrahendDate 减数
         * @return 相差的天数
         */
        public static Long days(Date minuendDate, Date subtrahendDate) {
            return ChronoUnit.DAYS.between(minuendDate.toInstant(), subtrahendDate.toInstant());
        }

        /**
         * 获取时间差 - 周
         *
         * @param minuendDate    被减数
         * @param subtrahendDate 减数
         * @return 相关的周数
         */
        public static Long weeks(Date minuendDate, Date subtrahendDate) {
            return ChronoUnit.WEEKS.between(minuendDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(),
                    subtrahendDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());
        }

        /**
         * 获取时间差 - 月
         *
         * @param minuendDate    被减数
         * @param subtrahendDate 减数
         * @return 相关的月数
         */
        public static Long months(Date minuendDate, Date subtrahendDate) {
            return ChronoUnit.MONTHS.between(minuendDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(),
                    subtrahendDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());
        }

        /**
         * 获取时间差 - 年
         *
         * @param minuendDate    被减数
         * @param subtrahendDate 减数
         * @return 相关的年数
         */
        public static Long years(Date minuendDate, Date subtrahendDate) {
            return ChronoUnit.YEARS.between(minuendDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(),
                    subtrahendDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());
        }

        /**
         * 判断当前日期是否在给定的日期之间
         *
         * @param beginDate 起始日期
         * @param endDate   结束日期
         * @return 是/否
         */
        public static boolean is(Date beginDate, Date endDate) {
            if (Objects.isNull(beginDate) || Objects.isNull(endDate)) {
                return false;
            }

            return new Date().before(endDate) && new Date().after(beginDate);
        }
    }

    /**
     * 时间转换，应该包含以下类型：
     * date <-> instant
     * date <-> localDateTime
     * date -> localDate
     * date -> localTime
     * <p>
     * 由于 {@link LocalDate} 只包含日期信息，固这里不提供 {@link LocalDate} 转换为 {@link Date} 的方法
     * <p>
     * 同理，{@link LocalTime} 只包含时间信息。
     */
    public static class Convert {

        /**
         * 把 Instant 转换为 Date
         *
         * @param instant JDK8中新时间类
         * @return 旧时间类Date
         */
        public static Date instant2Date(Instant instant) {
            return Date.from(instant);
        }

        /**
         * 把 Date 转换为 Instant
         *
         * @param date 旧时间类
         * @return JDK8中新时间类
         */
        public static Instant date2Instant(Date date) {
            return date.toInstant();
        }

        /**
         * 把 LocalDateTime 转换为 Date
         *
         * @param localDateTime JDK8中新时间类
         * @return 旧时间类Date
         */
        public static Date localDateTime2Date(LocalDateTime localDateTime) {
            return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
        }

        /**
         * 把 Date 转换为 LocalDateTime
         *
         * @param date 旧时间类
         * @return JDK8 中的新时间类
         */
        public static LocalDateTime date2LocalDateTime(Date date) {
            return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        }

        /**
         * 把 Date 转换为 LocalDate
         *
         * @param date 旧时间类
         * @return JDK8 中的新时间类
         */
        public static LocalDate date2LocalDate(Date date) {
            return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        }

        /**
         * 把 Date 转换为 LocalTime
         *
         * @param date 旧时间类
         * @return JDK8 中的新时间类
         */
        public static LocalTime date2LocalTime(Date date) {
            return date.toInstant().atZone(ZoneId.systemDefault()).toLocalTime();
        }
    }

    /**
     * 获取当前时刻所处指定单位的数值，如一天中的第几秒；一周中的第几天；一年中的第几天等
     */
    public static class Of {
        /**
         * 获取当前时刻所处一天中的秒数
         *
         * @param date 指定时刻
         * @return 秒数
         */
        public static int secondOfDay(Date date) {
            LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
            return localDateTime.get(ChronoField.SECOND_OF_DAY);
        }

        /**
         * 获取当前时刻所处一天中的分钟数
         *
         * @param date 指定时刻
         * @return 分钟数
         */
        public static int minuteOfDay(Date date) {
            LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
            return localDateTime.get(ChronoField.MINUTE_OF_DAY);
        }

        /**
         * 获取当前时刻所处一天中的小时数
         *
         * @param date 指定时刻
         * @return 小时数
         */
        public static int hourOfDay(Date date) {
            LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
            return localDateTime.get(ChronoField.CLOCK_HOUR_OF_DAY);
        }

        // 其它的请举一反三

        /**
         * 获取当前时刻所处一年中的天数
         *
         * @param date 指定时刻
         * @return 天数
         */
        public static int dayOfYear(Date date) {
            LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
            return localDateTime.getDayOfYear();
        }
    }

    /**
     * 日期格式化
     *
     * @param instant 新时间实例，同 Date
     * @return 格式化结果
     */
    public static String format(Instant instant) {
        return format(instant, DEFAULT_PATTERN);
    }

    /**
     * 日期格式化
     *
     * @param instant 新时间实例，同 Date
     * @param pattern 格式
     * @return 格式化结果
     */
    public static String format(Instant instant, String pattern) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        return instant.atZone(ZoneId.systemDefault()).format(formatter);
    }

    /**
     * 日期格式化
     *
     * @param date 新时间实例，同 Date
     * @return 格式化结果
     */
    public static String format(Date date) {
        return format(date, DEFAULT_PATTERN);
    }

    /**
     * 日期格式化
     *
     * @param date    日期
     * @param pattern 格式
     * @return 格式化结果
     */
    public static String format(Date date, String pattern) {
        return format(date.toInstant(), pattern);
    }

    /**
     * 日期格式化
     *
     * @param localDate 新日期实例
     * @param pattern   格式
     * @return 格式化结果
     */
    public static String format(LocalDate localDate, String pattern) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        return localDate.format(formatter);
    }

    /**
     * 日期格式化
     *
     * @param localDateTime 新日期实例
     * @param pattern       格式
     * @return 格式化结果
     */
    public static String format(LocalDateTime localDateTime, String pattern) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        return localDateTime.format(formatter);
    }

    /**
     * 日期解析
     * <p>
     * 注意这里的默认格式是 yyyy-MM-dd HH:mm:ss.SSS
     *
     * @param time 要解析 的时间
     * @return 解析结果
     */
    public static Date parse(String time) {
        return parse(time, DEFAULT_PATTERN);
    }

    /**
     * 指定格式解析时间
     *
     * @param time    要解析的时间
     * @param pattern 格式
     * @return 解析结果
     */
    public static Date parse(String time, String pattern) {
        return Date.from(
                LocalDateTime.parse(time, DateTimeFormatter.ofPattern(pattern))
                        .atZone(ZoneId.systemDefault())
                        .toInstant()
        );
    }

    /**
     * 将指定的时间长度及类型转换为可读内容，原理是将指定的时间值添加到当前时间上，然后求两个时间差
     * <p>
     * 如 121, 秒 转换为 "2分1秒"
     *
     * @param value      指定时间数值
     * @param chronoUnit 指定时间类型
     * @return 转换结果
     */
    public static String toShowString(Integer value, ChronoUnit chronoUnit) {
        LocalDateTime now = LocalDateTime.now();
        // 在当前时间上添加指定单位的时间
        LocalDateTime plus = now.plus(value, chronoUnit);

        long days = ChronoUnit.DAYS.between(now, plus);
        // 计算不足 1 天的小时数
        long hours = ChronoUnit.HOURS.between(now, plus) % 24;
        // 计算不足 1 小时的分钟数
        long minutes = ChronoUnit.MINUTES.between(now, plus) % 60;
        // 计算不足 1 分钟的秒数
        long seconds = ChronoUnit.SECONDS.between(now, plus) % 60;

        return (days > 0 ? (days + "天") : "") +
                (hours > 0 ? (hours + "小时") : "") +
                (minutes > 0 ? (minutes + "分") : "") +
                (seconds > 0 ? (seconds + "秒") : "");

    }

}
